The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.


Cytoscape(www.cytoscape.org) is one of the most popular applications for network analysis and visualization. In this workshop, we will demonstrate new capabilities to integrate Cytoscape into programmatic workflows and pipelines using R. We will begin with an overview of network biology themes and concepts, and then we will translate these into Cytoscape terms for practical applications. The bulk of the workshop will be a hands-on demonstration of accessing and controlling Cytoscape from R to perform a network analysis of tumor expression data.

Installation

if(!"RCy3" %in% installed.packages()){
    install.packages("BiocManager")
    BiocManager::install("RCy3")
}
library(RCy3)

if(!"RColorBrewer" %in% installed.packages()){
    install.packages("RColorBrewer")
}
library(RColorBrewer)

if(!"EnrichmentBrowser" %in% installed.packages()){
    install.packages("EnrichmentBrowser")
}
library(EnrichmentBrowser)

if(!"gProfileR" %in% installed.packages()){
    install.packages("gProfileR")
}
library(gProfileR)

Required Software

The whole point of RCy3 is to connect with Cytoscape. You will need to install and launch Cytoscape:

Make sure that Cytoscape is running

 cytoscapePing ()
 cytoscapeVersionInfo ()

To see all the functions available in the RCy3 package:

help(package=RCy3)

Also install additional Cytoscape apps that will be used in this tutorial:

#available in Cytoscape 3.7.0 and above
installApp('STRINGapp')  
installApp('aMatReader')
installApp('clusterMaker2')

Example Data Set

We downloaded gene expression data from the Ovarian Serous Cystadenocarcinoma project of The Cancer Genome Atlas (TCGA)[@TCGA], http://cancergenome.nih.gov via the Genomic Data Commons (GDC) portal[@GDC] on 2017-06-14 using TCGABiolinks R package[@TCGABiolinks]. The data includes 300 samples available as RNA-seq data, with reads mapped to a reference genome using MapSplice[@MapSplice] and read counts per transcript determined using the RSEM method[@RSEM]. RNA-seq data are labeled as ‘RNA-Seq V2’, see details at: https://wiki.nci.nih.gov/display/TCGA/RNASeq+Version+2). The RNA-SeqV2 data consists of raw counts similar to regular RNA-seq but RSEM (RNA-Seq by Expectation Maximization) data can be used with the edgeR method. The expression dataset of 300 tumours, with 79 classified as Immunoreactive, 72 classified as Mesenchymal, 69 classified as Differentiated, and 80 classified as Proliferative samples(class definitions were obtained from Verhaak et al.[@OV] Supplementary Table 1, third column). RNA-seq read counts were converted to CPM values and genes with CPM > 1 in at least 50 of the samples are retained for further study (50 is the minimal sample size in the classes). The data was normalized and differential expression was calculated for each cancer class relative to the rest of the samples.

There are two data files: 1. Expression matrix - containing the normalized expression for each gene across all 300 samples. 1. Gene ranks - containing the p-values, FDR and foldchange values for the 4 comparisons (mesenchymal vs rest, differential vs rest, proliferative vs rest and immunoreactive vs rest)

The following commands will download the necessary data files to the working directory. This entire tutorial assumes that the data is in that same working directory, and this is also where files will be exported. Let’s start by finding out what the working directory is. If you need to change it, this is done by the command setwd(), which you will see commented out in the snippet below:

print(getwd())
##setwd(path)
#define locations for data and download
url_exp <- "https://raw.githubusercontent.com/cytoscape/cytoscape-tutorials/gh-pages/presentations/modules/RCy3_ExampleData/data/TCGA_OV_RNAseq_expression.txt"
path_exp <- file.path(getwd(), "TCGA_OV_RNAseq_expression.txt")

url_scores <-"https://github.com/cytoscape/cytoscape-tutorials/blob/gh-pages/presentations/modules/RCy3_ExampleData/data/TCGA_OV_RNAseq_All_edgeR_scores.txt?raw=true"
path_scores <- file.path(getwd(), "TCGA_OV_RNAseq_All_edgeR_scores.txt")

#download files
download.file(url_exp, path_exp)
download.file(url_scores, path_scores)

Now we can read the downloaded files into R:

RNASeq_expression_matrix <- read.table(path_exp, header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)

RNASeq_gene_scores <- read.table(path_scores, header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)

Use Case 2 - Which genes have similar expression.

Instead of querying existing resources look for correlations in your own dataset to find out which genes have similar expression. There are many tools that can analyze your data for correlation. A popular tool is Weighted Gene Correlation Network Analysis (WGCNA)[@wgcna] which takes expression data and calculates functional modules. As a simple example we can transform our expression dataset into a correlation matrix.

Using the Cytoscape App, aMatReader[@amatreader], we transform our adjacency matrix into an interaction network. First we filter the correlation matrix to contain only the strongest connections (for example, only correlations greater than 0.9).


RNASeq_expression <- RNASeq_expression_matrix[,3:ncol(RNASeq_expression_matrix)]

rownames(RNASeq_expression) <- RNASeq_expression_matrix$Name
RNAseq_correlation_matrix <- cor(t(RNASeq_expression), method="pearson")

#set the diagonal of matrix to zero - eliminate self-correlation
RNAseq_correlation_matrix[ 
  row(RNAseq_correlation_matrix) == col(RNAseq_correlation_matrix) ] <- 0

# set all correlations that are less than 0.9 to zero
RNAseq_correlation_matrix[which(RNAseq_correlation_matrix<0.90)] <- 0

#get rid of rows and columns that have no correlations with the above thresholds
RNAseq_correlation_matrix <- RNAseq_correlation_matrix[which(rowSums(RNAseq_correlation_matrix) != 0),
                          which(colSums(RNAseq_correlation_matrix) !=0)]

#write out the correlation file
correlation_filename <- file.path(getwd(), "TCGA_OV_RNAseq_expression_correlation_matrix.txt") 
write.table(RNAseq_correlation_matrix,  file = correlation_filename, col.names  = TRUE, row.names = FALSE, sep = "\t", quote=FALSE)

Use the CyRest call to access the aMatReader functionality:

amat_url <- "aMatReader/v1/import"
amat_params = list(files = list(correlation_filename),
                   delimiter = "TAB",
                   undirected = FALSE,
                   ignoreZeros = TRUE,
                   interactionName = "correlated with",
                   rowNames = FALSE
                   )
 
response <- cyrestPOST(operation = amat_url, body = amat_params, base.url = "http://localhost:1234")

current_network_id <- response$data["suid"]
#relayout network
layoutNetwork('cose',
              network = as.numeric(current_network_id))
renameNetwork(title ="Coexpression_network_pear0_95_new",
              network = as.numeric(current_network_id))

Modify the visualization to see where each genes is predominantly expressed. Look at the 4 different p-values associated with each gene and color the nodes with the type associated with the lowest FDR.

Load in the scoring data. Specify the cancer type where the genes has the lowest FDR value:

nodes_in_network <- rownames(RNAseq_correlation_matrix)

#add an additional column to the gene scores table to indicate in which samples
# the gene is significant
node_class <- vector(length = length(nodes_in_network),mode = "character")
for(i in 1:length(nodes_in_network)){
  current_row <- which(RNASeq_gene_scores$Name == nodes_in_network[i])
  min_pvalue <- min(RNASeq_gene_scores[current_row,
                                       grep(colnames(RNASeq_gene_scores), pattern = "FDR")])
  if(RNASeq_gene_scores$FDR.mesen[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"mesen",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.diff[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"diff",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.prolif[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"prolif",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.immuno[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"immuno",sep = " ")
  }
}
node_class <- trimws(node_class)
node_class_df <-data.frame(name=nodes_in_network, node_class,stringsAsFactors = FALSE)

head(node_class_df)

Map the new node attribute and the all the gene scores to the network.

loadTableData(RNASeq_gene_scores,table.key.column = "name",data.key.column = "Name")  #default data.frame key is row.names

loadTableData(node_class_df,table.key.column = "name",data.key.column = "name")  #default data.frame key is row.names

Create a color mapping for the different cancer types:

#create a new mapping with the different types
unique_types <- sort(unique(node_class))

coul = brewer.pal(4, "Set1") 
 
# I can add more tones to this palette :
coul = colorRampPalette(coul)(length(unique_types))

setNodeColorMapping(table.column = "node_class",table.column.values = unique_types,
                    colors = coul,mapping.type = "d")
correlation_network_png_file_name <- file.path(getwd(), "correlation_network.png")
if(file.exists(correlation_network_png_file_name)){
  #cytoscape hangs waiting for user response if file already exists.  Remove it first
  file.remove(correlation_network_png_file_name)
  } 

#export the network
exportImage(correlation_network_png_file_name, type = "png")

Cluster the Network:

#make sure it is set to the right network
  setCurrentNetwork(network = getNetworkName(suid=as.numeric(current_network_id)))

  #cluster the network
  clustermaker_url <- paste("cluster mcl network=SUID:",current_network_id, sep="")
  commandsGET(clustermaker_url)
  
  #get the clustering results
  default_node_table <- getTableColumns(table= "node",network = as.numeric(current_network_id))
 
  head(default_node_table)

Perform pathway Enrichment on one of the clusters using g:Profiler[@gprofiler]. g:Profiler is an online functional enrichment web service that will take your gene list and return the set of enriched pathways. For automated analysis g:Profiler has created an R library to interact with it directly from R instead of using the web page.

Create a function to call g:Profiler and convert the returned results into a generic enrichment map input file:

tryCatch(expr = { library("gProfileR")}, 
         error = function(e) { install.packages("gProfileR")}, finally = library("gProfileR"))

#function to run gprofiler using the gprofiler library
# 
# The function takes the returned gprofiler results and formats it to the generic EM input file
#
# function returns a data frame in the generic EM file format.
runGprofiler <- function(genes,current_organism = "hsapiens", 
                         significant_only = F, set_size_max = 200, 
                         set_size_min = 3, filter_gs_size_min = 5 , exclude_iea = F){
  
  gprofiler_results <- gprofiler(genes ,
                                 significant=significant_only,ordered_query = F,
                                exclude_iea=exclude_iea,max_set_size = set_size_max,
                                 min_set_size = set_size_min,
                                 correction_method = "fdr",
                                 organism = current_organism,
                                src_filter = c("GO:BP","REAC"))
  
  #filter results
  gprofiler_results <- gprofiler_results[which(gprofiler_results[,'term.size'] >= 3
                                        & gprofiler_results[,'overlap.size'] >= filter_gs_size_min ),]
  
  # gProfileR returns corrected p-values only.  Set p-value to corrected p-value
  if(dim(gprofiler_results)[1] > 0){
    em_results <- cbind(gprofiler_results[,
                                c("term.id","term.name","p.value","p.value")], 1,
                                gprofiler_results[,"intersection"])
  colnames(em_results) <- c("Name","Description", "pvalue","qvalue","phenotype","genes")
  
  return(em_results)
  } else {
    return("no gprofiler results for supplied query")
  }
}

Run g:Profiler. g:Profiler will return a set of pathways and functions that are found to be enriched in our query set of genes.

  current_cluster <- "1"
  #select all the nodes in cluster 1
  selectednodes <- selectNodes(current_cluster, by.col="__mclCluster")
  
  #create a subnetwork with cluster 1
  subnetwork_suid <- createSubnetwork(nodes="selected")
  
  renameNetwork("Cluster1_Subnetwork", network=as.numeric(subnetwork_suid))
  
  subnetwork_node_table <- getTableColumns(table= "node",network = as.numeric(subnetwork_suid))

  em_results <- runGprofiler(subnetwork_node_table$name)
  
 #write out the g:Profiler results
 em_results_filename <-file.path(getwd(),
                            paste("gprofiler_cluster",current_cluster,"enr_results.txt",sep="_"))

  write.table(em_results,em_results_filename,col.name=TRUE,sep="\t",row.names=FALSE,quote=FALSE)
  
 
  head(em_results)

Create an enrichment map with the returned g:Profiler results. An enrichment map is a different sort of network. Instead of nodes representing genes, nodes represent pathways or functions. Edges between these pathways or functions represent shared genes or pathway crosstalk. An enrichment map is a way to visualize your enrichment results to help reduce redundancy and uncover main themes. Pathways can also be explored in detail using the features available through the App in Cytoscape.

 em_command = paste('enrichmentmap build analysisType="generic" ', 
                   'pvalue=',"0.05", 'qvalue=',"0.05",
                   'similaritycutoff=',"0.25",
                   'coeffecients=',"JACCARD",
                   'enrichmentsDataset1=',em_results_filename ,
                   sep=" ")

  #enrichment map command will return the suid of newly created network.
  em_network_suid <- commandsRun(em_command)
  
  renameNetwork("Cluster1_enrichmentmap", network=as.numeric(em_network_suid))

Export image of resulting Enrichment map.

cluster1em_png_file_name <- file.path(getwd(),"cluster1em.png")
if(file.exists(cluster1em_png_file_name)){
  #cytoscape hangs waiting for user response if file already exists.  Remove it first
  file.remove(cluster1em_png_file_name)
  } 

#export the network
exportImage(cluster1em_png_file_name, type = "png")

Annotate the Enrichment map to get the general themes that are found in the enrichment results of cluster 1

#get the column from the nodetable and node table
  nodetable_colnames <- getTableColumnNames(table="node",  network =  as.numeric(em_network_suid))

  descr_attrib <- nodetable_colnames[grep(nodetable_colnames, pattern = "GS_DESCR")]

  #Autoannotate the network
  autoannotate_url <- paste("autoannotate annotate-clusterBoosted labelColumn=", descr_attrib," maxWords=3 ", sep="")
    current_name <-commandsGET(autoannotate_url)

Export image of resulting Annotated Enrichment map.

cluster1em_annot_png_file_name <- file.path(getwd(), "cluster1em_annot.png")
if(file.exists(cluster1em_annot_png_file_name)){
  #cytoscape hangs waiting for user response if file already exists.  Remove it first
  file.remove(cluster1em_annot_png_file_name)
  } 

#export the network
exportImage(cluster1em_annot_png_file_name, type = "png")

Dense networks small or large never look like network figures we so often see in journals. A lot of manual tweaking, reorganization and optimization is involved in getting that perfect figure ready network. The above network is what the network starts as. The below figure is what it can look like after a few minutes of manual re-organiazation. (individual clusters were selected from the auto annotate panel and separated from other clusters)

LS0tCnRpdGxlOiAiVG9wIGdlbmVzIGFuZCBjb2V4cHJlc3Npb24iCmF1dGhvcjogImJ5IFJ1dGggSXNzZXJsaW4sIEtyaXN0aW5hIEhhbnNwZXJzIgpwYWNrYWdlOiBSQ3kzCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJub25lIgojICBwZGZfZG9jdW1lbnQ6CiMgICAgdG9jOiB0cnVlICAKLS0tCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBldmFsPUZBTFNFCikKYGBgCipUaGUgUiBtYXJrZG93biBpcyBhdmFpbGFibGUgZnJvbSB0aGUgcHVsbGRvd24gbWVudSBmb3IqIENvZGUgKmF0IHRoZSB1cHBlci1yaWdodCwgY2hvb3NlICJEb3dubG9hZCBSbWQiLCBvciBbZG93bmxvYWQgdGhlIFJtZCBmcm9tIEdpdEh1Yl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9Ub3AtZ2VuZXMtYW5kLWNvZXhwcmVzc2lvbi5SbWQpLioKCjxociAvPgpDeXRvc2NhcGUod3d3LmN5dG9zY2FwZS5vcmcpIGlzIG9uZSBvZiB0aGUgbW9zdCBwb3B1bGFyIGFwcGxpY2F0aW9ucyBmb3IgbmV0d29yayBhbmFseXNpcyBhbmQgdmlzdWFsaXphdGlvbi4gSW4gdGhpcyB3b3Jrc2hvcCwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBuZXcgY2FwYWJpbGl0aWVzIHRvIGludGVncmF0ZSBDeXRvc2NhcGUgaW50byBwcm9ncmFtbWF0aWMgd29ya2Zsb3dzIGFuZCBwaXBlbGluZXMgdXNpbmcgUi4gV2Ugd2lsbCBiZWdpbiB3aXRoIGFuIG92ZXJ2aWV3IG9mIG5ldHdvcmsgYmlvbG9neSB0aGVtZXMgYW5kIGNvbmNlcHRzLCBhbmQgdGhlbiB3ZSB3aWxsIHRyYW5zbGF0ZSB0aGVzZSBpbnRvIEN5dG9zY2FwZSB0ZXJtcyBmb3IgcHJhY3RpY2FsIGFwcGxpY2F0aW9ucy4gVGhlIGJ1bGsgb2YgdGhlIHdvcmtzaG9wIHdpbGwgYmUgYSBoYW5kcy1vbiBkZW1vbnN0cmF0aW9uIG9mIGFjY2Vzc2luZyBhbmQgY29udHJvbGxpbmcgQ3l0b3NjYXBlIGZyb20gUiB0byBwZXJmb3JtIGEgbmV0d29yayBhbmFseXNpcyBvZiB0dW1vciBleHByZXNzaW9uIGRhdGEuCgojIEluc3RhbGxhdGlvbgpgYGB7cn0KaWYoISJSQ3kzIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKXsKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSQ3kzIikKfQpsaWJyYXJ5KFJDeTMpCgppZighIlJDb2xvckJyZXdlciIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKQp9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKaWYoISJFbnJpY2htZW50QnJvd3NlciIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJFbnJpY2htZW50QnJvd3NlciIpCn0KbGlicmFyeShFbnJpY2htZW50QnJvd3NlcikKCmlmKCEiZ1Byb2ZpbGVSIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKXsKICAgIGluc3RhbGwucGFja2FnZXMoImdQcm9maWxlUiIpCn0KbGlicmFyeShnUHJvZmlsZVIpCmBgYAoKIyBSZXF1aXJlZCBTb2Z0d2FyZQpUaGUgd2hvbGUgcG9pbnQgb2YgUkN5MyBpcyB0byBjb25uZWN0IHdpdGggQ3l0b3NjYXBlLiBZb3Ugd2lsbCBuZWVkIHRvIGluc3RhbGwgYW5kIGxhdW5jaCBDeXRvc2NhcGU6IAogICAgCiogRG93bmxvYWQgdGhlIGxhdGVzdCBDeXRvc2NhcGUgZnJvbSBodHRwOi8vd3d3LmN5dG9zY2FwZS5vcmcvZG93bmxvYWQucGhwCiogQ29tcGxldGUgaW5zdGFsbGF0aW9uIHdpemFyZAoqIExhdW5jaCBDeXRvc2NhcGUgCgoqKk1ha2Ugc3VyZSB0aGF0IEN5dG9zY2FwZSBpcyBydW5uaW5nKioKYGBge3IgZXZhbD1GQUxTRX0KIGN5dG9zY2FwZVBpbmcgKCkKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQogY3l0b3NjYXBlVmVyc2lvbkluZm8gKCkKYGBgCgpUbyBzZWUgYWxsIHRoZSBmdW5jdGlvbnMgYXZhaWxhYmxlIGluIHRoZSBSQ3kzIHBhY2thZ2U6IApgYGB7cn0KaGVscChwYWNrYWdlPVJDeTMpCmBgYAoKQWxzbyBpbnN0YWxsIGFkZGl0aW9uYWwgQ3l0b3NjYXBlIGFwcHMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhpcyB0dXRvcmlhbDoKCmBgYHtyfQojYXZhaWxhYmxlIGluIEN5dG9zY2FwZSAzLjcuMCBhbmQgYWJvdmUKaW5zdGFsbEFwcCgnU1RSSU5HYXBwJykgIAppbnN0YWxsQXBwKCdhTWF0UmVhZGVyJykKaW5zdGFsbEFwcCgnY2x1c3Rlck1ha2VyMicpCmBgYAoKIyBFeGFtcGxlIERhdGEgU2V0CldlIGRvd25sb2FkZWQgZ2VuZSBleHByZXNzaW9uIGRhdGEgZnJvbSB0aGUgT3ZhcmlhbiBTZXJvdXMgQ3lzdGFkZW5vY2FyY2lub21hIHByb2plY3Qgb2YgVGhlIENhbmNlciBHZW5vbWUgQXRsYXMgKFRDR0EpW0BUQ0dBXSwgaHR0cDovL2NhbmNlcmdlbm9tZS5uaWguZ292IHZpYSB0aGUgR2Vub21pYyBEYXRhIENvbW1vbnMgKEdEQykgcG9ydGFsW0BHRENdIG9uIDIwMTctMDYtMTQgdXNpbmcgVENHQUJpb2xpbmtzIFIgcGFja2FnZVtAVENHQUJpb2xpbmtzXS4gVGhlIGRhdGEgaW5jbHVkZXMgMzAwIHNhbXBsZXMgYXZhaWxhYmxlIGFzIFJOQS1zZXEgZGF0YSwgd2l0aCByZWFkcyBtYXBwZWQgdG8gYSByZWZlcmVuY2UgZ2Vub21lIHVzaW5nIE1hcFNwbGljZVtATWFwU3BsaWNlXSBhbmQgcmVhZCBjb3VudHMgcGVyIHRyYW5zY3JpcHQgZGV0ZXJtaW5lZCB1c2luZyB0aGUgUlNFTSBtZXRob2RbQFJTRU1dLiBSTkEtc2VxIGRhdGEgYXJlIGxhYmVsZWQgYXMg4oCYUk5BLVNlcSBWMuKAmSwgc2VlIGRldGFpbHMgYXQ6IGh0dHBzOi8vd2lraS5uY2kubmloLmdvdi9kaXNwbGF5L1RDR0EvUk5BU2VxK1ZlcnNpb24rMikuIFRoZSBSTkEtU2VxVjIgZGF0YSBjb25zaXN0cyBvZiByYXcgY291bnRzIHNpbWlsYXIgdG8gcmVndWxhciBSTkEtc2VxIGJ1dCBSU0VNIChSTkEtU2VxIGJ5IEV4cGVjdGF0aW9uIE1heGltaXphdGlvbikgZGF0YSBjYW4gYmUgdXNlZCB3aXRoIHRoZSBlZGdlUiBtZXRob2QuIFRoZSBleHByZXNzaW9uIGRhdGFzZXQgb2YgMzAwIHR1bW91cnMsIHdpdGggNzkgY2xhc3NpZmllZCBhcyBJbW11bm9yZWFjdGl2ZSwgNzIgY2xhc3NpZmllZCBhcyBNZXNlbmNoeW1hbCwgNjkgY2xhc3NpZmllZCBhcyBEaWZmZXJlbnRpYXRlZCwgYW5kIDgwIGNsYXNzaWZpZWQgYXMgUHJvbGlmZXJhdGl2ZSBzYW1wbGVzKGNsYXNzIGRlZmluaXRpb25zIHdlcmUgb2J0YWluZWQgZnJvbSBWZXJoYWFrIGV0IGFsLltAT1ZdIFN1cHBsZW1lbnRhcnkgVGFibGUgMSwgdGhpcmQgY29sdW1uKS4gUk5BLXNlcSByZWFkIGNvdW50cyB3ZXJlIGNvbnZlcnRlZCB0byBDUE0gdmFsdWVzIGFuZCBnZW5lcyB3aXRoIENQTSA+IDEgaW4gYXQgbGVhc3QgNTAgb2YgdGhlIHNhbXBsZXMgYXJlIHJldGFpbmVkIGZvciBmdXJ0aGVyIHN0dWR5ICg1MCBpcyB0aGUgbWluaW1hbCBzYW1wbGUgc2l6ZSBpbiB0aGUgY2xhc3NlcykuICBUaGUgZGF0YSB3YXMgbm9ybWFsaXplZCBhbmQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gd2FzIGNhbGN1bGF0ZWQgZm9yIGVhY2ggY2FuY2VyIGNsYXNzIHJlbGF0aXZlIHRvIHRoZSByZXN0IG9mIHRoZSBzYW1wbGVzLiAKClRoZXJlIGFyZSB0d28gZGF0YSBmaWxlczoKIDEuIEV4cHJlc3Npb24gbWF0cml4IC0gY29udGFpbmluZyB0aGUgbm9ybWFsaXplZCBleHByZXNzaW9uIGZvciBlYWNoIGdlbmUgYWNyb3NzIGFsbCAzMDAgc2FtcGxlcy4KIDEuIEdlbmUgcmFua3MgLSBjb250YWluaW5nIHRoZSBwLXZhbHVlcywgRkRSIGFuZCBmb2xkY2hhbmdlIHZhbHVlcyBmb3IgdGhlIDQgY29tcGFyaXNvbnMgKG1lc2VuY2h5bWFsIHZzIHJlc3QsIGRpZmZlcmVudGlhbCB2cyByZXN0LCBwcm9saWZlcmF0aXZlIHZzIHJlc3QgYW5kIGltbXVub3JlYWN0aXZlIHZzIHJlc3QpCgpUaGUgZm9sbG93aW5nIGNvbW1hbmRzIHdpbGwgZG93bmxvYWQgdGhlIG5lY2Vzc2FyeSBkYXRhIGZpbGVzIHRvIHRoZSB3b3JraW5nIGRpcmVjdG9yeS4gVGhpcyBlbnRpcmUgdHV0b3JpYWwgYXNzdW1lcyB0aGF0IHRoZSBkYXRhIGlzIGluIHRoYXQgc2FtZSB3b3JraW5nIGRpcmVjdG9yeSwgYW5kIHRoaXMgaXMgYWxzbyB3aGVyZSBmaWxlcyB3aWxsIGJlIGV4cG9ydGVkLiBMZXQncyBzdGFydCBieSBmaW5kaW5nIG91dCB3aGF0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSBpcy4gSWYgeW91IG5lZWQgdG8gY2hhbmdlIGl0LCB0aGlzIGlzIGRvbmUgYnkgdGhlIGNvbW1hbmQgc2V0d2QoKSwgd2hpY2ggeW91IHdpbGwgc2VlIGNvbW1lbnRlZCBvdXQgaW4gdGhlIHNuaXBwZXQgYmVsb3c6CgpgYGB7cn0KcHJpbnQoZ2V0d2QoKSkKIyNzZXR3ZChwYXRoKQpgYGAKCmBgYHtyfQojZGVmaW5lIGxvY2F0aW9ucyBmb3IgZGF0YSBhbmQgZG93bmxvYWQKdXJsX2V4cCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtdHV0b3JpYWxzL2doLXBhZ2VzL3ByZXNlbnRhdGlvbnMvbW9kdWxlcy9SQ3kzX0V4YW1wbGVEYXRhL2RhdGEvVENHQV9PVl9STkFzZXFfZXhwcmVzc2lvbi50eHQiCnBhdGhfZXhwIDwtIGZpbGUucGF0aChnZXR3ZCgpLCAiVENHQV9PVl9STkFzZXFfZXhwcmVzc2lvbi50eHQiKQoKdXJsX3Njb3JlcyA8LSJodHRwczovL2dpdGh1Yi5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS10dXRvcmlhbHMvYmxvYi9naC1wYWdlcy9wcmVzZW50YXRpb25zL21vZHVsZXMvUkN5M19FeGFtcGxlRGF0YS9kYXRhL1RDR0FfT1ZfUk5Bc2VxX0FsbF9lZGdlUl9zY29yZXMudHh0P3Jhdz10cnVlIgpwYXRoX3Njb3JlcyA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgIlRDR0FfT1ZfUk5Bc2VxX0FsbF9lZGdlUl9zY29yZXMudHh0IikKCiNkb3dubG9hZCBmaWxlcwpkb3dubG9hZC5maWxlKHVybF9leHAsIHBhdGhfZXhwKQpkb3dubG9hZC5maWxlKHVybF9zY29yZXMsIHBhdGhfc2NvcmVzKQpgYGAKCmBgYHtyIGxpYiwgZWNobz1GQUxTRSwgcmVzdWx0cz0iaGlkZSIsIGNhY2hlPUZBTFNFfQp3ZCA8LSBnZXR3ZCgpCmBgYAoKTm93IHdlIGNhbiByZWFkIHRoZSBkb3dubG9hZGVkIGZpbGVzIGludG8gUjoKYGBge3J9ClJOQVNlcV9leHByZXNzaW9uX21hdHJpeCA8LSByZWFkLnRhYmxlKHBhdGhfZXhwLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpSTkFTZXFfZ2VuZV9zY29yZXMgPC0gcmVhZC50YWJsZShwYXRoX3Njb3JlcywgaGVhZGVyID0gVFJVRSwgc2VwID0gIlx0IiwgcXVvdGU9IlwiIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCiMgVXNlIENhc2UgMSAtIEhvdyBhcmUgbXkgdG9wIGdlbmVzIHJlbGF0ZWQ/CgpPbWljcyBkYXRhIC0gSSBoYXZlIGEgKmZpbGwgaW4gdGhlIGJsYW5rKiAobWljcm9hcnJheSwgUk5BU2VxLCBQcm90ZW9taWNzLCBBVEFDc2VxLCBNaWNyb1JOQSwgR1dBUyAuLi4pIGRhdGFzZXQuIEkgaGF2ZSBub3JtYWxpemVkIGFuZCBzY29yZWQgbXkgZGF0YS4gSG93IGRvIEkgb3ZlcmxheSBteSBkYXRhIG9uIGV4aXN0aW5nIGludGVyYWN0aW9uIGRhdGE/IAoKR2V0IGEgc3Vic2V0IG9mIGdlbmVzIG9mIGludGVyZXN0IGZyb20gb3VyIHNjb3JlZCBkYXRhOgpgYGB7cn0KdG9wX21lc2VuY2h5bWFsX2dlbmVzIDwtIFJOQVNlcV9nZW5lX3Njb3Jlc1t3aGljaChSTkFTZXFfZ2VuZV9zY29yZXMkRkRSLm1lc2VuIDwgMC4wNSAmIFJOQVNlcV9nZW5lX3Njb3JlcyRsb2dGQy5tZXNlbiA+IDIpLF0KaGVhZCh0b3BfbWVzZW5jaHltYWxfZ2VuZXMpCmBgYAoKV2UgYXJlIGdvaW5nIHRvIHF1ZXJ5IHRoZSBTdHJpbmcgRGF0YWJhc2UgdG8gZ2V0IGFsbCBpbnRlcmFjdGlvbnMgZm91bmQgZm9yIG91ciBzZXQgb2YgdG9wIE1lc2VuY2h5bWFsIGdlbmVzLgoKUmVtaW5kZXI6IHRvIHNlZSB0aGUgcGFyYW1ldGVycyByZXF1aXJlZCBieSB0aGUgc3RyaW5nIGZ1bmN0aW9uIG9yIHRvIGZpbmQgdGhlIHJpZ2h0IHN0cmluZyBmdW5jdGlvbiB5b3UgY2FuIHVzZSBjb21tYW5kc0hlbHAuCmBgYHtyIGV2YWw9RkFMU0V9CmNvbW1hbmRzSGVscCgiaGVscCBzdHJpbmciKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CmNvbW1hbmRzSGVscCgiaGVscCBzdHJpbmcgcHJvdGVpbiBxdWVyeSIpCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KbWVzZW5fc3RyaW5nX2ludGVyYWN0aW9uX2NtZCA8LSBwYXN0ZSgnc3RyaW5nIHByb3RlaW4gcXVlcnkgdGF4b25JRD05NjA2IGxpbWl0PTE1MCBjdXRvZmY9MC45IHF1ZXJ5PSInLHBhc3RlKHRvcF9tZXNlbmNoeW1hbF9nZW5lcyROYW1lLCBjb2xsYXBzZT0iLCIpLCciJyxzZXA9IiIpCmNvbW1hbmRzR0VUKG1lc2VuX3N0cmluZ19pbnRlcmFjdGlvbl9jbWQpCmBgYAoKR2V0IGEgc2NyZWVuc2hvdCBvZiB0aGUgaW5pdGlhbCBuZXR3b3JrOgpgYGB7ciBpbml0aWFsX3N0cmluZ19uZXR3b3JrX3NjcmVlbnNob3QsIGluY2x1ZGU9VFJVRX0KaW5pdGlhbF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lIDwtIGZpbGUucGF0aChnZXR3ZCgpLCAiaW5pdGlhbF9zdHJpbmdfbmV0d29yay5wbmciKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CmlmKGZpbGUuZXhpc3RzKGluaXRpYWxfc3RyaW5nX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSkpewogICNjeXRvc2NhcGUgaGFuZ3Mgd2FpdGluZyBmb3IgdXNlciByZXNwb25zZSBpZiBmaWxlIGFscmVhZHkgZXhpc3RzLiAgUmVtb3ZlIGl0IGZpcnN0CiAgcmVzcG9uc2UgPC0gZmlsZS5yZW1vdmUoaW5pdGlhbF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQp9IAoKcmVzcG9uc2UgPC0gZXhwb3J0SW1hZ2UoaW5pdGlhbF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lLCB0eXBlID0gInBuZyIpCmBgYAoKYGBge3IgaW5pdGlhbHN0cmluZ25ldHdvcmssIGVjaG89RkFMU0UsIGZpZy5jYXA9IkluaXRpYWwgbmV0d29yayByZXR1cm5lZCBieSBTdHJpbmcgZnJvbSBvdXIgc2V0IG9mIE1lc2VuY2h5bWFsIHF1ZXJ5IGdlbmVzIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaW5pdGlhbF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQpgYGAKCkxheW91dCB0aGUgbmV0d29yazoKYGBge3IgZXZhbD1GQUxTRX0KbGF5b3V0TmV0d29yaygnZm9yY2UtZGlyZWN0ZWQnKQpgYGAKCkNoZWNrIHdoYXQgb3RoZXIgbGF5b3V0IGFsZ29yaXRobXMgYXJlIGF2YWlsYWJsZSB0byB0cnkgb3V0OgpgYGB7ciBldmFsPUZBTFNFfQpnZXRMYXlvdXROYW1lcygpCmBgYAoKR2V0IHRoZSBwYXJhbWV0ZXJzIGZvciBhIHNwZWNpZmljIGxheW91dDoKYGBge3IgZXZhbD1GQUxTRX0KZ2V0TGF5b3V0UHJvcGVydHlOYW1lcyhsYXlvdXQubmFtZT0nZm9yY2UtZGlyZWN0ZWQnKQpgYGAKClJlLWxheW91dCB0aGUgbmV0d29yayB1c2luZyB0aGUgZm9yY2UgZGlyZWN0ZWQgbGF5b3V0IGJ1dCBzcGVjaWZ5IHNvbWUgb2YgdGhlIHBhcmFtZXRlcnM6CmBgYHtyIGV2YWw9RkFMU0V9CmxheW91dE5ldHdvcmsoJ2ZvcmNlLWRpcmVjdGVkIGRlZmF1bHRTcHJpbmdDb2VmZmljaWVudD0wLjAwMDAwMDggZGVmYXVsdFNwcmluZ0xlbmd0aD03MCcpCmBgYAoKR2V0IGEgc2NyZWVuc2hvdCBvZiB0aGUgcmUtbGFpZCBvdXQgbmV0d29yazoKYGBge3IgcmVsYXlvdXRfc3RyaW5nX25ldHdvcmtfc2NyZWVuc2hvdCwgaW5jbHVkZT1UUlVFfQpyZWxheW91dF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lIDwtIGZpbGUucGF0aChnZXR3ZCgpLCAicmVsYXlvdXRfc3RyaW5nX25ldHdvcmsucG5nIikKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQppZihmaWxlLmV4aXN0cyhyZWxheW91dF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKSl7CiAgI2N5dG9zY2FwZSBoYW5ncyB3YWl0aW5nIGZvciB1c2VyIHJlc3BvbnNlIGlmIGZpbGUgYWxyZWFkeSBleGlzdHMuICBSZW1vdmUgaXQgZmlyc3QKICByZXNwb25zZTwtIGZpbGUucmVtb3ZlKHJlbGF5b3V0X3N0cmluZ19uZXR3b3JrX3BuZ19maWxlX25hbWUpCiAgfSAKcmVzcG9uc2UgPC0gZXhwb3J0SW1hZ2UocmVsYXlvdXRfc3RyaW5nX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSwgdHlwZSA9ICJwbmciKQpgYGAKCmBgYHtyIHJlbGF5b3V0c3RyaW5nbmV0d29yaywgZWNobz1GQUxTRSwgZmlnLmNhcD0iSW5pdGlhbCBuZXR3b3JrIHJldHVybmVkIGJ5IFN0cmluZyBmcm9tIG91ciBzZXQgb2YgTWVzZW5jaHltYWwgcXVlcnkgZ2VuZXMifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhyZWxheW91dF9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQpgYGAKCk92ZXJsYXkgb3VyIGV4cHJlc3Npb24gZGF0YSBvbiB0aGUgU3RyaW5nIG5ldHdvcmsuICAKVG8gZG8gdGhpcyB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBsb2FkVGFibGVEYXRhIGZ1bmN0aW9uIGZyb20gUkN5My4gSXQgaXMgaW1wb3J0YW50IHRvIG1ha2Ugc3VyZSB0aGF0ICB0aGF0IHlvdXIgaWRlbnRpZmllcnMgdHlwZXMgbWF0Y2ggdXAuICBZb3UgY2FuIGNoZWNrIHdoYXQgaXMgdXNlZCBieSBTdHJpbmcgYnkgcHVsbGluZyBpbiB0aGUgY29sdW1uIG5hbWVzIG9mIHRoZSBub2RlIGF0dHJpYnV0ZSB0YWJsZS4KYGBge3IgZXZhbD1GQUxTRX0KZ2V0VGFibGVDb2x1bW5OYW1lcygnbm9kZScpCmBgYAoKSWYgeW91IGFyZSB1bnN1cmUgb2Ygd2hhdCBlYWNoIGNvbHVtbiBpcyBhbmQgd2FudCB0byBmdXJ0aGVyIHZlcmlmeSB0aGUgY29sdW1uIHRvIHVzZSB5b3UgY2FuIGFsc28gcHVsbCBpbiB0aGUgZW50aXJlIG5vZGUgYXR0cmlidXRlIHRhYmxlOgpgYGB7ciBldmFsPUZBTFNFfQpub2RlX2F0dHJpYnV0ZV90YWJsZV90b3BtZXNlbiA8LSBnZXRUYWJsZUNvbHVtbnModGFibGU9Im5vZGUiKQpoZWFkKG5vZGVfYXR0cmlidXRlX3RhYmxlX3RvcG1lc2VuWywzOjddKQpgYGAKClRoZSBjb2x1bW4gImRpc3BsYXkgbmFtZSIgY29udGFpbnMgSEdOQyBnZW5lIG5hbWVzIHdoaWNoIGFyZSBhbHNvIGZvdW5kIGluIG91ciBPdmFyaWFuIENhbmNlciBkYXRhc2V0LgoKVG8gaW1wb3J0IG91ciBleHByZXNzaW9uIGRhdGEgd2Ugd2lsbCBtYXRjaCBvdXIgZGF0YXNldCB0byB0aGUgImRpc3BsYXkgbmFtZSIgbm9kZSBhdHRyaWJ1dGU6CmBgYHtyIGV2YWw9RkFMU0V9Cj9sb2FkVGFibGVEYXRhCgpsb2FkVGFibGVEYXRhKFJOQVNlcV9nZW5lX3Njb3Jlcyx0YWJsZS5rZXkuY29sdW1uID0gImRpc3BsYXkgbmFtZSIsZGF0YS5rZXkuY29sdW1uID0gIk5hbWUiKSAgI2RlZmF1bHQgZGF0YS5mcmFtZSBrZXkgaXMgcm93Lm5hbWVzCmBgYAoKTW9kaWZ5IHRoZSBWaXN1YWwgU3R5bGUKQ3JlYXRlIHlvdXIgb3duIHZpc3VhbCBzdHlsZSB0byB2aXN1YWxpemUgeW91ciBleHByZXNzaW9uIGRhdGEgb24gdGhlIFN0cmluZyBuZXR3b3JrLiAKClN0YXJ0IHdpdGggYSBkZWZhdWx0IHN0eWxlOgpgYGB7ciBldmFsPUZBTFNFfQpzdHlsZS5uYW1lID0gIk1lc2VuY2h5bWFsU3R5bGUiCmRlZmF1bHRzLmxpc3QgPC0gbGlzdChOT0RFX1NIQVBFPSJlbGxpcHNlIiwKICAgICAgICAgICAgICAgICBOT0RFX1NJWkU9NjAsCiAgICAgICAgICAgICAgICAgTk9ERV9GSUxMX0NPTE9SPSIjQUFBQUFBIiwKICAgICAgICAgICAgICAgICBFREdFX1RSQU5TUEFSRU5DWT0xMjApCm5vZGUubGFiZWwubWFwIDwtIG1hcFZpc3VhbFByb3BlcnR5KCdub2RlIGxhYmVsJywnZGlzcGxheSBuYW1lJywncCcpICMgcCBmb3IgcGFzc3Rocm91Z2g7IG5vdGhpbmcgZWxzZSBuZWVkZWQKY3JlYXRlVmlzdWFsU3R5bGUoc3R5bGUubmFtZSwgZGVmYXVsdHMubGlzdCwgbGlzdChub2RlLmxhYmVsLm1hcCkpCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKYGBgCgpVcGRhdGUgeW91ciBjcmVhdGVkIHN0eWxlIHdpdGggYSBtYXBwaW5nIGZvciB0aGUgTWVzZW5jaHltYWwgbG9nRkMgZXhwcmVzc2lvbi4gVGhlIGZpcnN0IHN0ZXAgaXMgdG8gZ3JhYiB0aGUgY29sdW1uIGRhdGEgZnJvbSBDeXRvc2NhcGUgKHdlIGNhbiByZXVzZSB0aGUgbm9kZV9hdHRyaWJ1dGUgdGFibGUgY29uY2VwdCBmcm9tIGFib3ZlIGJ1dCB3ZSBoYXZlIHRvIGNhbGwgdGhlIGZ1bmN0aW9uIGFnYWluIGFzIHdlIGhhdmUgc2luY2UgYWRkZWQgb3VyIGV4cHJlc3Npb24gZGF0YSkgYW5kIHB1bGwgb3V0IHRoZSBtaW4gYW5kIG1heCB0byBkZWZpbmUgb3VyIGRhdGEgbWFwcGluZyByYW5nZSBvZiB2YWx1ZXMuCgoqKk5vdGUqKjogeW91IGNvdWxkIGRlZmluZSB0aGUgbWluIGFuZCBtYXggYmFzZWQgb24gdGhlIGVudGlyZSBkYXRhc2V0IG9yIGp1c3QgdGhlIHN1YnNldCB0aGF0IGlzIHJlcHJlc2VudGVkIGluIEN5dG9zY2FwZSBjdXJyZW50bHkuIFRoZSB0d28gbWV0aG9kcyB3aWxsIGdpdmUgeW91IGRpZmZlcmVudCByZXN1bHRzLiBJZiB5b3UgaW50ZW5kIG9uIGNvbXBhcmluZyBkaWZmZXJlbnQgbmV0d29ya3MgY3JlYXRlZCB3aXRoIHRoZSBzYW1lIGRhdGFzZXQgdGhlbiBpdCBpcyBiZXN0IHRvIGNhbGN1bGF0ZSB0aGUgbWluIGFuZCBtYXggZnJvbSB0aGUgZW50aXJlIGRhdGFzZXQgYXMgb3Bwb3NlZCB0byBhIHN1YnNldC4gCmBgYHtyfQptaW4ubWVzZW4ubG9nZmMgPSBtaW4oUk5BU2VxX2dlbmVfc2NvcmVzJGxvZ0ZDLm1lc2VuLG5hLnJtPVRSVUUpCm1heC5tZXNlbi5sb2dmYyA9IG1heChSTkFTZXFfZ2VuZV9zY29yZXMkbG9nRkMubWVzZW4sbmEucm09VFJVRSkKZGF0YS52YWx1ZXMgPSBjKG1pbi5tZXNlbi5sb2dmYywwLG1heC5tZXNlbi5sb2dmYykKYGBgCgpOZXh0LCB3ZSB1c2UgdGhlIFJDb2xvckJyZXdlciBwYWNrYWdlIHRvIGhlbHAgdXMgcGljayBnb29kIGNvbG9ycyB0byBwYWlyIHdpdGggb3VyIGRhdGEgdmFsdWVzOgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmRpc3BsYXkuYnJld2VyLmFsbChsZW5ndGgoZGF0YS52YWx1ZXMpLCBjb2xvcmJsaW5kRnJpZW5kbHk9VFJVRSwgdHlwZT0iZGl2IikgIyBkaXYscXVhbCxzZXEsYWxsCm5vZGUuY29sb3JzIDwtIGMocmV2KGJyZXdlci5wYWwobGVuZ3RoKGRhdGEudmFsdWVzKSwgIlJkQnUiKSkpCmBgYAoKTWFwIHRoZSBjb2xvcnMgdG8gb3VyIGRhdGEgdmFsdWUgYW5kIHVwZGF0ZSBvdXIgdmlzdWFsIHN0eWxlOgpgYGB7ciBldmFsPUZBTFNFfSAKc2V0Tm9kZUNvbG9yTWFwcGluZygibG9nRkMubWVzZW4iLCBkYXRhLnZhbHVlcywgbm9kZS5jb2xvcnMsIHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKYGBgCgpSZW1lbWJlciwgU3RyaW5nIGluY2x1ZGVzIHlvdXIgcXVlcnkgcHJvdGVpbnMgYXMgd2VsbCBhcyBvdGhlciBwcm90ZWlucyB0aGF0IGFzc29jaWF0ZSB3aXRoIHlvdXIgcXVlcnkgcHJvdGVpbnMgKGluY2x1ZGluZyB0aGUgc3Ryb25nZXN0IGNvbm5lY3Rpb24gZmlyc3QpLiBOb3QgYWxsIG9mIHRoZSBwcm90ZWlucyBpbiB0aGlzIG5ldHdvcmsgYXJlIHlvdXIgdG9wIGhpdHMuIEhvdyBjYW4gd2UgdmlzdWFsaXplIHdoaWNoIHByb3RlaW5zIGFyZSBvdXIgdG9wIE1lc2VuY2h5bWFsIGhpdHM/CgpBZGQgYSBkaWZmZXJlbnQgYm9yZGVyIGNvbG9yIG9yIGNoYW5nZSB0aGUgbm9kZSBzaGFwZSBmb3Igb3VyIHRvcCBoaXRzOgpgYGB7ciBldmFsPUZBTFNFfQpnZXROb2RlU2hhcGVzKCkKCiNzZWxlY3QgdGhlIE5vZGVzIG9mIGludGVyZXN0CiNzZWxlY3ROb2RlKG5vZGVzID0gdG9wX21lc2VuY2h5bWFsX2dlbmVzJE5hbWUsIGJ5LmNvbD0iZGlzcGxheSBuYW1lIikKc2V0Tm9kZVNoYXBlQnlwYXNzKG5vZGUubmFtZXMgPSB0b3BfbWVzZW5jaHltYWxfZ2VuZXMkTmFtZSwgbmV3LnNoYXBlcyA9ICJUUklBTkdMRSIpCmBgYAoKQ2hhbmdlIHRoZSBzaXplIG9mIHRoZSBub2RlIHRvIGJlIGNvcnJlbGF0ZWQgd2l0aCB0aGUgTWVzZW5jaHltYWwgcC12YWx1ZToKYGBge3IgZXZhbD1GQUxTRX0Kc2V0Tm9kZVNpemVNYXBwaW5nKHRhYmxlLmNvbHVtbiA9ICdMUi5tZXNlbicsIAogICAgICAgICAgICAgICAgICAgdGFibGUuY29sdW1uLnZhbHVlcyA9IGMobWluKFJOQVNlcV9nZW5lX3Njb3JlcyRMUi5tZXNlbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihSTkFTZXFfZ2VuZV9zY29yZXMkTFIubWVzZW4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heChSTkFTZXFfZ2VuZV9zY29yZXMkTFIubWVzZW4pKSwgCiAgICAgICAgICAgICAgICAgICBzaXplcyA9IGMoMzAsIDYwLCAxNTApLG1hcHBpbmcudHlwZSA9ICJjIiwgc3R5bGUubmFtZSA9IHN0eWxlLm5hbWUpCmBgYAoKR2V0IGEgc2NyZWVuc2hvdCBvZiB0aGUgcmVzdWx0aW5nIG5ldHdvcms6CmBgYHtyIG1lc2VuX3N0cmluZ19uZXR3b3JrX3NjcmVlbnNob3QsIGluY2x1ZGU9VFJVRX0KbWVzZW5fc3RyaW5nX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgIm1lc2VuX3N0cmluZ19uZXR3b3JrLnBuZyIpCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KaWYoZmlsZS5leGlzdHMobWVzZW5fc3RyaW5nX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSkpewogICNjeXRvc2NhcGUgaGFuZ3Mgd2FpdGluZyBmb3IgdXNlciByZXNwb25zZSBpZiBmaWxlIGFscmVhZHkgZXhpc3RzLiAgUmVtb3ZlIGl0IGZpcnN0CiAgcmVzcG9uc2U8LSBmaWxlLnJlbW92ZShtZXNlbl9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQogIH0gCnJlc3BvbnNlIDwtIGV4cG9ydEltYWdlKG1lc2VuX3N0cmluZ19uZXR3b3JrX3BuZ19maWxlX25hbWUsIHR5cGUgPSAicG5nIikKYGBgCgpgYGB7ciBtZXNlbnN0cmluZ25ldHdvcmssIGVjaG89RkFMU0UsIGZpZy5jYXA9IkZvcm1hdHRlZCBTdHJpbmcgbmV0d29yayBmcm9tIG91ciBzZXQgb2YgTWVzZW5jaHltYWwgcXVlcnkgZ2VuZXMuICBBbm5vdGF0ZWQgd2l0aCBvdXIgZXhwcmVzc2luIGRhdGEifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhtZXNlbl9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQpgYGAKCiMgVXNlIENhc2UgMiAtIFdoaWNoIGdlbmVzIGhhdmUgc2ltaWxhciBleHByZXNzaW9uLgoKSW5zdGVhZCBvZiBxdWVyeWluZyBleGlzdGluZyByZXNvdXJjZXMgbG9vayBmb3IgY29ycmVsYXRpb25zIGluIHlvdXIgb3duIGRhdGFzZXQgdG8gZmluZCBvdXQgd2hpY2ggZ2VuZXMgaGF2ZSBzaW1pbGFyIGV4cHJlc3Npb24uICBUaGVyZSBhcmUgbWFueSB0b29scyB0aGF0IGNhbiBhbmFseXplIHlvdXIgZGF0YSBmb3IgY29ycmVsYXRpb24uICBBIHBvcHVsYXIgdG9vbCBpcyBXZWlnaHRlZCBHZW5lIENvcnJlbGF0aW9uIE5ldHdvcmsgQW5hbHlzaXMgKFdHQ05BKVtAd2djbmFdIHdoaWNoIHRha2VzIGV4cHJlc3Npb24gZGF0YSBhbmQgY2FsY3VsYXRlcyBmdW5jdGlvbmFsIG1vZHVsZXMuICBBcyBhIHNpbXBsZSBleGFtcGxlIHdlIGNhbiB0cmFuc2Zvcm0gb3VyIGV4cHJlc3Npb24gZGF0YXNldCBpbnRvIGEgY29ycmVsYXRpb24gbWF0cml4LiAgCgpVc2luZyB0aGUgQ3l0b3NjYXBlIEFwcCwgYU1hdFJlYWRlcltAYW1hdHJlYWRlcl0sIHdlIHRyYW5zZm9ybSBvdXIgYWRqYWNlbmN5IG1hdHJpeCBpbnRvIGFuIGludGVyYWN0aW9uIG5ldHdvcmsuIEZpcnN0IHdlIGZpbHRlciB0aGUgY29ycmVsYXRpb24gbWF0cml4IHRvIGNvbnRhaW4gb25seSB0aGUgc3Ryb25nZXN0IGNvbm5lY3Rpb25zIChmb3IgZXhhbXBsZSwgb25seSBjb3JyZWxhdGlvbnMgZ3JlYXRlciB0aGFuIDAuOSkuIApgYGB7cn0KClJOQVNlcV9leHByZXNzaW9uIDwtIFJOQVNlcV9leHByZXNzaW9uX21hdHJpeFssMzpuY29sKFJOQVNlcV9leHByZXNzaW9uX21hdHJpeCldCgpyb3duYW1lcyhSTkFTZXFfZXhwcmVzc2lvbikgPC0gUk5BU2VxX2V4cHJlc3Npb25fbWF0cml4JE5hbWUKUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCA8LSBjb3IodChSTkFTZXFfZXhwcmVzc2lvbiksIG1ldGhvZD0icGVhcnNvbiIpCgojc2V0IHRoZSBkaWFnb25hbCBvZiBtYXRyaXggdG8gemVybyAtIGVsaW1pbmF0ZSBzZWxmLWNvcnJlbGF0aW9uClJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXhbIAogIHJvdyhSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4KSA9PSBjb2woUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCkgXSA8LSAwCgojIHNldCBhbGwgY29ycmVsYXRpb25zIHRoYXQgYXJlIGxlc3MgdGhhbiAwLjkgdG8gemVybwpSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4W3doaWNoKFJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXg8MC45MCldIDwtIDAKCiNnZXQgcmlkIG9mIHJvd3MgYW5kIGNvbHVtbnMgdGhhdCBoYXZlIG5vIGNvcnJlbGF0aW9ucyB3aXRoIHRoZSBhYm92ZSB0aHJlc2hvbGRzClJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXggPC0gUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeFt3aGljaChyb3dTdW1zKFJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXgpICE9IDApLAogICAgICAgICAgICAgICAgICAgICAgICAgIHdoaWNoKGNvbFN1bXMoUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCkgIT0wKV0KCiN3cml0ZSBvdXQgdGhlIGNvcnJlbGF0aW9uIGZpbGUKY29ycmVsYXRpb25fZmlsZW5hbWUgPC0gZmlsZS5wYXRoKGdldHdkKCksICJUQ0dBX09WX1JOQXNlcV9leHByZXNzaW9uX2NvcnJlbGF0aW9uX21hdHJpeC50eHQiKSAKd3JpdGUudGFibGUoUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCwgIGZpbGUgPSBjb3JyZWxhdGlvbl9maWxlbmFtZSwgY29sLm5hbWVzICA9IFRSVUUsIHJvdy5uYW1lcyA9IEZBTFNFLCBzZXAgPSAiXHQiLCBxdW90ZT1GQUxTRSkKCmBgYAoKVXNlIHRoZSBDeVJlc3QgY2FsbCB0byBhY2Nlc3MgdGhlIGFNYXRSZWFkZXIgZnVuY3Rpb25hbGl0eToKYGBge3IgZXZhbD1GQUxTRX0KYW1hdF91cmwgPC0gImFNYXRSZWFkZXIvdjEvaW1wb3J0IgphbWF0X3BhcmFtcyA9IGxpc3QoZmlsZXMgPSBsaXN0KGNvcnJlbGF0aW9uX2ZpbGVuYW1lKSwKICAgICAgICAgICAgICAgICAgIGRlbGltaXRlciA9ICJUQUIiLAogICAgICAgICAgICAgICAgICAgdW5kaXJlY3RlZCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgaWdub3JlWmVyb3MgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25OYW1lID0gImNvcnJlbGF0ZWQgd2l0aCIsCiAgICAgICAgICAgICAgICAgICByb3dOYW1lcyA9IEZBTFNFCiAgICAgICAgICAgICAgICAgICApCiAKcmVzcG9uc2UgPC0gY3lyZXN0UE9TVChvcGVyYXRpb24gPSBhbWF0X3VybCwgYm9keSA9IGFtYXRfcGFyYW1zLCBiYXNlLnVybCA9ICJodHRwOi8vbG9jYWxob3N0OjEyMzQiKQoKY3VycmVudF9uZXR3b3JrX2lkIDwtIHJlc3BvbnNlJGRhdGFbInN1aWQiXQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CiNyZWxheW91dCBuZXR3b3JrCmxheW91dE5ldHdvcmsoJ2Nvc2UnLAogICAgICAgICAgICAgIG5ldHdvcmsgPSBhcy5udW1lcmljKGN1cnJlbnRfbmV0d29ya19pZCkpCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KcmVuYW1lTmV0d29yayh0aXRsZSA9IkNvZXhwcmVzc2lvbl9uZXR3b3JrX3BlYXIwXzk1X25ldyIsCiAgICAgICAgICAgICAgbmV0d29yayA9IGFzLm51bWVyaWMoY3VycmVudF9uZXR3b3JrX2lkKSkKYGBgCgoKTW9kaWZ5IHRoZSB2aXN1YWxpemF0aW9uIHRvIHNlZSB3aGVyZSBlYWNoIGdlbmVzIGlzIHByZWRvbWluYW50bHkgZXhwcmVzc2VkLiBMb29rIGF0IHRoZSA0IGRpZmZlcmVudCBwLXZhbHVlcyBhc3NvY2lhdGVkIHdpdGggZWFjaCBnZW5lIGFuZCBjb2xvciB0aGUgbm9kZXMgd2l0aCB0aGUgdHlwZSBhc3NvY2lhdGVkIHdpdGggdGhlIGxvd2VzdCBGRFIuCgpMb2FkIGluIHRoZSBzY29yaW5nIGRhdGEuIFNwZWNpZnkgdGhlIGNhbmNlciB0eXBlIHdoZXJlIHRoZSBnZW5lcyBoYXMgdGhlIGxvd2VzdCBGRFIgdmFsdWU6CmBgYHtyfQpub2Rlc19pbl9uZXR3b3JrIDwtIHJvd25hbWVzKFJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXgpCgojYWRkIGFuIGFkZGl0aW9uYWwgY29sdW1uIHRvIHRoZSBnZW5lIHNjb3JlcyB0YWJsZSB0byBpbmRpY2F0ZSBpbiB3aGljaCBzYW1wbGVzCiMgdGhlIGdlbmUgaXMgc2lnbmlmaWNhbnQKbm9kZV9jbGFzcyA8LSB2ZWN0b3IobGVuZ3RoID0gbGVuZ3RoKG5vZGVzX2luX25ldHdvcmspLG1vZGUgPSAiY2hhcmFjdGVyIikKZm9yKGkgaW4gMTpsZW5ndGgobm9kZXNfaW5fbmV0d29yaykpewogIGN1cnJlbnRfcm93IDwtIHdoaWNoKFJOQVNlcV9nZW5lX3Njb3JlcyROYW1lID09IG5vZGVzX2luX25ldHdvcmtbaV0pCiAgbWluX3B2YWx1ZSA8LSBtaW4oUk5BU2VxX2dlbmVfc2NvcmVzW2N1cnJlbnRfcm93LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwKGNvbG5hbWVzKFJOQVNlcV9nZW5lX3Njb3JlcyksIHBhdHRlcm4gPSAiRkRSIildKQogIGlmKFJOQVNlcV9nZW5lX3Njb3JlcyRGRFIubWVzZW5bY3VycmVudF9yb3ddIDw9bWluX3B2YWx1ZSl7CiAgICBub2RlX2NsYXNzW2ldIDwtIHBhc3RlKG5vZGVfY2xhc3NbaV0sIm1lc2VuIixzZXAgPSAiICIpCiAgfQogIGlmKFJOQVNlcV9nZW5lX3Njb3JlcyRGRFIuZGlmZltjdXJyZW50X3Jvd10gPD1taW5fcHZhbHVlKXsKICAgIG5vZGVfY2xhc3NbaV0gPC0gcGFzdGUobm9kZV9jbGFzc1tpXSwiZGlmZiIsc2VwID0gIiAiKQogIH0KICBpZihSTkFTZXFfZ2VuZV9zY29yZXMkRkRSLnByb2xpZltjdXJyZW50X3Jvd10gPD1taW5fcHZhbHVlKXsKICAgIG5vZGVfY2xhc3NbaV0gPC0gcGFzdGUobm9kZV9jbGFzc1tpXSwicHJvbGlmIixzZXAgPSAiICIpCiAgfQogIGlmKFJOQVNlcV9nZW5lX3Njb3JlcyRGRFIuaW1tdW5vW2N1cnJlbnRfcm93XSA8PW1pbl9wdmFsdWUpewogICAgbm9kZV9jbGFzc1tpXSA8LSBwYXN0ZShub2RlX2NsYXNzW2ldLCJpbW11bm8iLHNlcCA9ICIgIikKICB9Cn0Kbm9kZV9jbGFzcyA8LSB0cmltd3Mobm9kZV9jbGFzcykKbm9kZV9jbGFzc19kZiA8LWRhdGEuZnJhbWUobmFtZT1ub2Rlc19pbl9uZXR3b3JrLCBub2RlX2NsYXNzLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCmhlYWQobm9kZV9jbGFzc19kZikKYGBgCgpNYXAgdGhlIG5ldyBub2RlIGF0dHJpYnV0ZSBhbmQgdGhlIGFsbCB0aGUgZ2VuZSBzY29yZXMgdG8gdGhlIG5ldHdvcmsuCmBgYHtyIGV2YWw9RkFMU0V9CmxvYWRUYWJsZURhdGEoUk5BU2VxX2dlbmVfc2NvcmVzLHRhYmxlLmtleS5jb2x1bW4gPSAibmFtZSIsZGF0YS5rZXkuY29sdW1uID0gIk5hbWUiKSAgI2RlZmF1bHQgZGF0YS5mcmFtZSBrZXkgaXMgcm93Lm5hbWVzCgpsb2FkVGFibGVEYXRhKG5vZGVfY2xhc3NfZGYsdGFibGUua2V5LmNvbHVtbiA9ICJuYW1lIixkYXRhLmtleS5jb2x1bW4gPSAibmFtZSIpICAjZGVmYXVsdCBkYXRhLmZyYW1lIGtleSBpcyByb3cubmFtZXMKYGBgCgpDcmVhdGUgYSBjb2xvciBtYXBwaW5nIGZvciB0aGUgZGlmZmVyZW50IGNhbmNlciB0eXBlczoKYGBge3IgZXZhbD1GQUxTRX0KI2NyZWF0ZSBhIG5ldyBtYXBwaW5nIHdpdGggdGhlIGRpZmZlcmVudCB0eXBlcwp1bmlxdWVfdHlwZXMgPC0gc29ydCh1bmlxdWUobm9kZV9jbGFzcykpCgpjb3VsID0gYnJld2VyLnBhbCg0LCAiU2V0MSIpIAogCiMgSSBjYW4gYWRkIG1vcmUgdG9uZXMgdG8gdGhpcyBwYWxldHRlIDoKY291bCA9IGNvbG9yUmFtcFBhbGV0dGUoY291bCkobGVuZ3RoKHVuaXF1ZV90eXBlcykpCgpzZXROb2RlQ29sb3JNYXBwaW5nKHRhYmxlLmNvbHVtbiA9ICJub2RlX2NsYXNzIix0YWJsZS5jb2x1bW4udmFsdWVzID0gdW5pcXVlX3R5cGVzLAogICAgICAgICAgICAgICAgICAgIGNvbG9ycyA9IGNvdWwsbWFwcGluZy50eXBlID0gImQiKQpgYGAKCmBgYHtyIGNvcnJlbGF0aW9ubmV0d29yaywgaW5jbHVkZT1UUlVFfQpjb3JyZWxhdGlvbl9uZXR3b3JrX3BuZ19maWxlX25hbWUgPC0gZmlsZS5wYXRoKGdldHdkKCksICJjb3JyZWxhdGlvbl9uZXR3b3JrLnBuZyIpCgpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CmlmKGZpbGUuZXhpc3RzKGNvcnJlbGF0aW9uX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSkpewogICNjeXRvc2NhcGUgaGFuZ3Mgd2FpdGluZyBmb3IgdXNlciByZXNwb25zZSBpZiBmaWxlIGFscmVhZHkgZXhpc3RzLiAgUmVtb3ZlIGl0IGZpcnN0CiAgZmlsZS5yZW1vdmUoY29ycmVsYXRpb25fbmV0d29ya19wbmdfZmlsZV9uYW1lKQogIH0gCgojZXhwb3J0IHRoZSBuZXR3b3JrCmV4cG9ydEltYWdlKGNvcnJlbGF0aW9uX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSwgdHlwZSA9ICJwbmciKQpgYGAKCmBgYHtyIGNvcnJlbGF0aW9uTmV0d29yazIsIGVjaG89RkFMU0UsIGZpZy5jYXA9IkV4YW1wbGUgY29ycmVsYXRpb24gbmV0d29yayBjcmVhdGVkIHVzaW5nIGFNYXRSZWFkZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhjb3JyZWxhdGlvbl9uZXR3b3JrX3BuZ19maWxlX25hbWUpCmBgYAoKQ2x1c3RlciB0aGUgTmV0d29yazogCmBgYHtyIGV2YWw9RkFMU0V9CiNtYWtlIHN1cmUgaXQgaXMgc2V0IHRvIHRoZSByaWdodCBuZXR3b3JrCiAgc2V0Q3VycmVudE5ldHdvcmsobmV0d29yayA9IGdldE5ldHdvcmtOYW1lKHN1aWQ9YXMubnVtZXJpYyhjdXJyZW50X25ldHdvcmtfaWQpKSkKCiAgI2NsdXN0ZXIgdGhlIG5ldHdvcmsKICBjbHVzdGVybWFrZXJfdXJsIDwtIHBhc3RlKCJjbHVzdGVyIG1jbCBuZXR3b3JrPVNVSUQ6IixjdXJyZW50X25ldHdvcmtfaWQsIHNlcD0iIikKICBjb21tYW5kc0dFVChjbHVzdGVybWFrZXJfdXJsKQogIAogICNnZXQgdGhlIGNsdXN0ZXJpbmcgcmVzdWx0cwogIGRlZmF1bHRfbm9kZV90YWJsZSA8LSBnZXRUYWJsZUNvbHVtbnModGFibGU9ICJub2RlIixuZXR3b3JrID0gYXMubnVtZXJpYyhjdXJyZW50X25ldHdvcmtfaWQpKQogCiAgaGVhZChkZWZhdWx0X25vZGVfdGFibGUpCmBgYAoKUGVyZm9ybSBwYXRod2F5IEVucmljaG1lbnQgb24gb25lIG9mIHRoZSBjbHVzdGVycyB1c2luZyBnOlByb2ZpbGVyW0BncHJvZmlsZXJdLiBnOlByb2ZpbGVyIGlzIGFuIG9ubGluZSBmdW5jdGlvbmFsIGVucmljaG1lbnQgd2ViIHNlcnZpY2UgdGhhdCB3aWxsIHRha2UgeW91ciBnZW5lIGxpc3QgYW5kIHJldHVybiB0aGUgc2V0IG9mIGVucmljaGVkIHBhdGh3YXlzLiBGb3IgYXV0b21hdGVkIGFuYWx5c2lzIGc6UHJvZmlsZXIgaGFzIGNyZWF0ZWQgYW4gUiBsaWJyYXJ5IHRvIGludGVyYWN0IHdpdGggaXQgZGlyZWN0bHkgZnJvbSBSIGluc3RlYWQgb2YgdXNpbmcgdGhlIHdlYiBwYWdlLiAKCkNyZWF0ZSBhIGZ1bmN0aW9uIHRvIGNhbGwgZzpQcm9maWxlciBhbmQgY29udmVydCB0aGUgcmV0dXJuZWQgcmVzdWx0cyBpbnRvIGEgZ2VuZXJpYyBlbnJpY2htZW50IG1hcCBpbnB1dCBmaWxlOgpgYGB7cn0KdHJ5Q2F0Y2goZXhwciA9IHsgbGlicmFyeSgiZ1Byb2ZpbGVSIil9LCAKICAgICAgICAgZXJyb3IgPSBmdW5jdGlvbihlKSB7IGluc3RhbGwucGFja2FnZXMoImdQcm9maWxlUiIpfSwgZmluYWxseSA9IGxpYnJhcnkoImdQcm9maWxlUiIpKQoKI2Z1bmN0aW9uIHRvIHJ1biBncHJvZmlsZXIgdXNpbmcgdGhlIGdwcm9maWxlciBsaWJyYXJ5CiMgCiMgVGhlIGZ1bmN0aW9uIHRha2VzIHRoZSByZXR1cm5lZCBncHJvZmlsZXIgcmVzdWx0cyBhbmQgZm9ybWF0cyBpdCB0byB0aGUgZ2VuZXJpYyBFTSBpbnB1dCBmaWxlCiMKIyBmdW5jdGlvbiByZXR1cm5zIGEgZGF0YSBmcmFtZSBpbiB0aGUgZ2VuZXJpYyBFTSBmaWxlIGZvcm1hdC4KcnVuR3Byb2ZpbGVyIDwtIGZ1bmN0aW9uKGdlbmVzLGN1cnJlbnRfb3JnYW5pc20gPSAiaHNhcGllbnMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHNpZ25pZmljYW50X29ubHkgPSBGLCBzZXRfc2l6ZV9tYXggPSAyMDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgc2V0X3NpemVfbWluID0gMywgZmlsdGVyX2dzX3NpemVfbWluID0gNSAsIGV4Y2x1ZGVfaWVhID0gRil7CiAgCiAgZ3Byb2ZpbGVyX3Jlc3VsdHMgPC0gZ3Byb2ZpbGVyKGdlbmVzICwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lnbmlmaWNhbnQ9c2lnbmlmaWNhbnRfb25seSxvcmRlcmVkX3F1ZXJ5ID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleGNsdWRlX2llYT1leGNsdWRlX2llYSxtYXhfc2V0X3NpemUgPSBzZXRfc2l6ZV9tYXgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9zZXRfc2l6ZSA9IHNldF9zaXplX21pbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVjdGlvbl9tZXRob2QgPSAiZmRyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JnYW5pc20gPSBjdXJyZW50X29yZ2FuaXNtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNyY19maWx0ZXIgPSBjKCJHTzpCUCIsIlJFQUMiKSkKICAKICAjZmlsdGVyIHJlc3VsdHMKICBncHJvZmlsZXJfcmVzdWx0cyA8LSBncHJvZmlsZXJfcmVzdWx0c1t3aGljaChncHJvZmlsZXJfcmVzdWx0c1ssJ3Rlcm0uc2l6ZSddID49IDMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgZ3Byb2ZpbGVyX3Jlc3VsdHNbLCdvdmVybGFwLnNpemUnXSA+PSBmaWx0ZXJfZ3Nfc2l6ZV9taW4gKSxdCiAgCiAgIyBnUHJvZmlsZVIgcmV0dXJucyBjb3JyZWN0ZWQgcC12YWx1ZXMgb25seS4gIFNldCBwLXZhbHVlIHRvIGNvcnJlY3RlZCBwLXZhbHVlCiAgaWYoZGltKGdwcm9maWxlcl9yZXN1bHRzKVsxXSA+IDApewogICAgZW1fcmVzdWx0cyA8LSBjYmluZChncHJvZmlsZXJfcmVzdWx0c1ssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygidGVybS5pZCIsInRlcm0ubmFtZSIsInAudmFsdWUiLCJwLnZhbHVlIildLCAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdwcm9maWxlcl9yZXN1bHRzWywiaW50ZXJzZWN0aW9uIl0pCiAgY29sbmFtZXMoZW1fcmVzdWx0cykgPC0gYygiTmFtZSIsIkRlc2NyaXB0aW9uIiwgInB2YWx1ZSIsInF2YWx1ZSIsInBoZW5vdHlwZSIsImdlbmVzIikKICAKICByZXR1cm4oZW1fcmVzdWx0cykKICB9IGVsc2UgewogICAgcmV0dXJuKCJubyBncHJvZmlsZXIgcmVzdWx0cyBmb3Igc3VwcGxpZWQgcXVlcnkiKQogIH0KfQpgYGAKClJ1biBnOlByb2ZpbGVyLiBnOlByb2ZpbGVyIHdpbGwgcmV0dXJuIGEgc2V0IG9mIHBhdGh3YXlzIGFuZCBmdW5jdGlvbnMgdGhhdCBhcmUgZm91bmQgdG8gYmUgZW5yaWNoZWQgaW4gb3VyIHF1ZXJ5IHNldCBvZiBnZW5lcy4KYGBge3IgZXZhbD1GQUxTRX0KICBjdXJyZW50X2NsdXN0ZXIgPC0gIjEiCiAgI3NlbGVjdCBhbGwgdGhlIG5vZGVzIGluIGNsdXN0ZXIgMQogIHNlbGVjdGVkbm9kZXMgPC0gc2VsZWN0Tm9kZXMoY3VycmVudF9jbHVzdGVyLCBieS5jb2w9Il9fbWNsQ2x1c3RlciIpCiAgCiAgI2NyZWF0ZSBhIHN1Ym5ldHdvcmsgd2l0aCBjbHVzdGVyIDEKICBzdWJuZXR3b3JrX3N1aWQgPC0gY3JlYXRlU3VibmV0d29yayhub2Rlcz0ic2VsZWN0ZWQiKQogIAogIHJlbmFtZU5ldHdvcmsoIkNsdXN0ZXIxX1N1Ym5ldHdvcmsiLCBuZXR3b3JrPWFzLm51bWVyaWMoc3VibmV0d29ya19zdWlkKSkKICAKICBzdWJuZXR3b3JrX25vZGVfdGFibGUgPC0gZ2V0VGFibGVDb2x1bW5zKHRhYmxlPSAibm9kZSIsbmV0d29yayA9IGFzLm51bWVyaWMoc3VibmV0d29ya19zdWlkKSkKCiAgZW1fcmVzdWx0cyA8LSBydW5HcHJvZmlsZXIoc3VibmV0d29ya19ub2RlX3RhYmxlJG5hbWUpCiAgCiAjd3JpdGUgb3V0IHRoZSBnOlByb2ZpbGVyIHJlc3VsdHMKIGVtX3Jlc3VsdHNfZmlsZW5hbWUgPC1maWxlLnBhdGgoZ2V0d2QoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJncHJvZmlsZXJfY2x1c3RlciIsY3VycmVudF9jbHVzdGVyLCJlbnJfcmVzdWx0cy50eHQiLHNlcD0iXyIpKQoKICB3cml0ZS50YWJsZShlbV9yZXN1bHRzLGVtX3Jlc3VsdHNfZmlsZW5hbWUsY29sLm5hbWU9VFJVRSxzZXA9Ilx0Iixyb3cubmFtZXM9RkFMU0UscXVvdGU9RkFMU0UpCiAgCiAKICBoZWFkKGVtX3Jlc3VsdHMpCmBgYAoKQ3JlYXRlIGFuIGVucmljaG1lbnQgbWFwIHdpdGggdGhlIHJldHVybmVkIGc6UHJvZmlsZXIgcmVzdWx0cy4gQW4gZW5yaWNobWVudCBtYXAgaXMgYSBkaWZmZXJlbnQgc29ydCBvZiBuZXR3b3JrLiAgSW5zdGVhZCBvZiBub2RlcyByZXByZXNlbnRpbmcgZ2VuZXMsIG5vZGVzIHJlcHJlc2VudCBwYXRod2F5cyBvciBmdW5jdGlvbnMuIEVkZ2VzIGJldHdlZW4gdGhlc2UgcGF0aHdheXMgb3IgZnVuY3Rpb25zIHJlcHJlc2VudCBzaGFyZWQgZ2VuZXMgb3IgcGF0aHdheSBjcm9zc3RhbGsuIEFuIGVucmljaG1lbnQgbWFwIGlzIGEgd2F5IHRvIHZpc3VhbGl6ZSB5b3VyIGVucmljaG1lbnQgcmVzdWx0cyB0byBoZWxwIHJlZHVjZSByZWR1bmRhbmN5IGFuZCB1bmNvdmVyIG1haW4gdGhlbWVzLiBQYXRod2F5cyBjYW4gYWxzbyBiZSBleHBsb3JlZCBpbiBkZXRhaWwgdXNpbmcgdGhlIGZlYXR1cmVzIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBBcHAgaW4gQ3l0b3NjYXBlLiAKYGBge3IgZXZhbD1GQUxTRX0KIGVtX2NvbW1hbmQgPSBwYXN0ZSgnZW5yaWNobWVudG1hcCBidWlsZCBhbmFseXNpc1R5cGU9ImdlbmVyaWMiICcsIAogICAgICAgICAgICAgICAgICAgJ3B2YWx1ZT0nLCIwLjA1IiwgJ3F2YWx1ZT0nLCIwLjA1IiwKICAgICAgICAgICAgICAgICAgICdzaW1pbGFyaXR5Y3V0b2ZmPScsIjAuMjUiLAogICAgICAgICAgICAgICAgICAgJ2NvZWZmZWNpZW50cz0nLCJKQUNDQVJEIiwKICAgICAgICAgICAgICAgICAgICdlbnJpY2htZW50c0RhdGFzZXQxPScsZW1fcmVzdWx0c19maWxlbmFtZSAsCiAgICAgICAgICAgICAgICAgICBzZXA9IiAiKQoKICAjZW5yaWNobWVudCBtYXAgY29tbWFuZCB3aWxsIHJldHVybiB0aGUgc3VpZCBvZiBuZXdseSBjcmVhdGVkIG5ldHdvcmsuCiAgZW1fbmV0d29ya19zdWlkIDwtIGNvbW1hbmRzUnVuKGVtX2NvbW1hbmQpCiAgCiAgcmVuYW1lTmV0d29yaygiQ2x1c3RlcjFfZW5yaWNobWVudG1hcCIsIG5ldHdvcms9YXMubnVtZXJpYyhlbV9uZXR3b3JrX3N1aWQpKQpgYGAKCkV4cG9ydCBpbWFnZSBvZiByZXN1bHRpbmcgRW5yaWNobWVudCBtYXAuCgpgYGB7ciBjbHVzdGVyMWVtLCBpbmNsdWRlPVRSVUV9CmNsdXN0ZXIxZW1fcG5nX2ZpbGVfbmFtZSA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwiY2x1c3RlcjFlbS5wbmciKQoKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQppZihmaWxlLmV4aXN0cyhjbHVzdGVyMWVtX3BuZ19maWxlX25hbWUpKXsKICAjY3l0b3NjYXBlIGhhbmdzIHdhaXRpbmcgZm9yIHVzZXIgcmVzcG9uc2UgaWYgZmlsZSBhbHJlYWR5IGV4aXN0cy4gIFJlbW92ZSBpdCBmaXJzdAogIGZpbGUucmVtb3ZlKGNsdXN0ZXIxZW1fcG5nX2ZpbGVfbmFtZSkKICB9IAoKI2V4cG9ydCB0aGUgbmV0d29yawpleHBvcnRJbWFnZShjbHVzdGVyMWVtX3BuZ19maWxlX25hbWUsIHR5cGUgPSAicG5nIikKYGBgCgpgYGB7ciBjbHVzdGVyMWVtZmlnLCBlY2hvPUZBTFNFLCBmaWcuY2FwPSJFeGFtcGxlIEVucmljaG1lbnQgTWFwIGNyZWF0ZWQgd2hlbiBydW5uaW5nIGFuIGVucmljaG1lbnQgYW5hbHlzaXMgdXNpbmcgZzpQcm9maWxlciB3aXRoIHRoZSBnZW5lcyB0aGF0IGFyZSBwYXJ0IG9mIGNsdXN0ZXIgMSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGNsdXN0ZXIxZW1fcG5nX2ZpbGVfbmFtZSkKYGBgCgpBbm5vdGF0ZSB0aGUgRW5yaWNobWVudCBtYXAgdG8gZ2V0IHRoZSBnZW5lcmFsIHRoZW1lcyB0aGF0IGFyZSBmb3VuZCBpbiB0aGUgZW5yaWNobWVudCByZXN1bHRzIG9mIGNsdXN0ZXIgMQpgYGB7ciBldmFsPUZBTFNFfQoKI2dldCB0aGUgY29sdW1uIGZyb20gdGhlIG5vZGV0YWJsZSBhbmQgbm9kZSB0YWJsZQogIG5vZGV0YWJsZV9jb2xuYW1lcyA8LSBnZXRUYWJsZUNvbHVtbk5hbWVzKHRhYmxlPSJub2RlIiwgIG5ldHdvcmsgPSAgYXMubnVtZXJpYyhlbV9uZXR3b3JrX3N1aWQpKQoKICBkZXNjcl9hdHRyaWIgPC0gbm9kZXRhYmxlX2NvbG5hbWVzW2dyZXAobm9kZXRhYmxlX2NvbG5hbWVzLCBwYXR0ZXJuID0gIkdTX0RFU0NSIildCgogICNBdXRvYW5ub3RhdGUgdGhlIG5ldHdvcmsKICBhdXRvYW5ub3RhdGVfdXJsIDwtIHBhc3RlKCJhdXRvYW5ub3RhdGUgYW5ub3RhdGUtY2x1c3RlckJvb3N0ZWQgbGFiZWxDb2x1bW49IiwgZGVzY3JfYXR0cmliLCIgbWF4V29yZHM9MyAiLCBzZXA9IiIpCiAgICBjdXJyZW50X25hbWUgPC1jb21tYW5kc0dFVChhdXRvYW5ub3RhdGVfdXJsKQoKYGBgCgpFeHBvcnQgaW1hZ2Ugb2YgcmVzdWx0aW5nIEFubm90YXRlZCBFbnJpY2htZW50IG1hcC4KCmBgYHtyIGNsdXN0ZXIxZW1hbm5vdCwgaW5jbHVkZT1UUlVFfQpjbHVzdGVyMWVtX2Fubm90X3BuZ19maWxlX25hbWUgPC0gZmlsZS5wYXRoKGdldHdkKCksICJjbHVzdGVyMWVtX2Fubm90LnBuZyIpCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KaWYoZmlsZS5leGlzdHMoY2x1c3RlcjFlbV9hbm5vdF9wbmdfZmlsZV9uYW1lKSl7CiAgI2N5dG9zY2FwZSBoYW5ncyB3YWl0aW5nIGZvciB1c2VyIHJlc3BvbnNlIGlmIGZpbGUgYWxyZWFkeSBleGlzdHMuICBSZW1vdmUgaXQgZmlyc3QKICBmaWxlLnJlbW92ZShjbHVzdGVyMWVtX2Fubm90X3BuZ19maWxlX25hbWUpCiAgfSAKCiNleHBvcnQgdGhlIG5ldHdvcmsKZXhwb3J0SW1hZ2UoY2x1c3RlcjFlbV9hbm5vdF9wbmdfZmlsZV9uYW1lLCB0eXBlID0gInBuZyIpCmBgYAoKYGBge3IgY2x1c3RlcjFlbWFubm90ZmlnLCBlY2hvPUZBTFNFLCBmaWcuY2FwPSJFeGFtcGxlIEFubm90YXRlZCBFbnJpY2htZW50IE1hcCBjcmVhdGVkIHdoZW4gcnVubmluZyBhbiBlbnJpY2htZW50IGFuYWx5c2lzIHVzaW5nIGc6UHJvZmlsZXIgd2l0aCB0aGUgZ2VuZXMgdGhhdCBhcmUgcGFydCBvZiBjbHVzdGVyIDEifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhjbHVzdGVyMWVtX2Fubm90X3BuZ19maWxlX25hbWUpCmBgYAoKRGVuc2UgbmV0d29ya3Mgc21hbGwgb3IgbGFyZ2UgbmV2ZXIgbG9vayBsaWtlIG5ldHdvcmsgZmlndXJlcyB3ZSBzbyBvZnRlbiBzZWUgaW4gam91cm5hbHMuIEEgbG90IG9mIG1hbnVhbCB0d2Vha2luZywgcmVvcmdhbml6YXRpb24gYW5kIG9wdGltaXphdGlvbiBpcyBpbnZvbHZlZCBpbiBnZXR0aW5nIHRoYXQgcGVyZmVjdCBmaWd1cmUgcmVhZHkgbmV0d29yay4gVGhlIGFib3ZlIG5ldHdvcmsgaXMgd2hhdCB0aGUgbmV0d29yayBzdGFydHMgYXMuIFRoZSBiZWxvdyBmaWd1cmUgaXMgd2hhdCBpdCBjYW4gbG9vayBsaWtlIGFmdGVyIGEgZmV3IG1pbnV0ZXMgb2YgbWFudWFsIHJlLW9yZ2FuaWF6YXRpb24uICAoaW5kaXZpZHVhbCBjbHVzdGVycyB3ZXJlIHNlbGVjdGVkIGZyb20gdGhlIGF1dG8gYW5ub3RhdGUgcGFuZWwgYW5kIHNlcGFyYXRlZCBmcm9tIG90aGVyIGNsdXN0ZXJzKQoKPGNlbnRlcj4KIVtdKGh0dHBzOi8vY3l0b3NjYXBlLmdpdGh1Yi5pby9jeXRvc2NhcGUtYXV0b21hdGlvbi9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL1RvcC1nZW5lcy1hbmQtY29leHByZXNzaW9uL2NsdXN0ZXIxZW1fYW5ub3RfbWFuLnBuZykKPC9jZW50ZXI+